#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Comb. Browser of commented files.

Implements utility for commenting any files. Unicode comments supported.
Comb also reads Descript.ion files, created by FAR application, but utilizes 
its own text files "desc.utf" to store info. "Desc.utf" file is created in 
current working directory and can be edited by means of Comb or a text editor.

There are two graphical modes available: a short list of commented files only,
and a full one, listing all files in directory.

Basic browsing features are supported, i.e. changing working directory.

"""

import os
import sys
import codecs
import re
import mimetypes
import subprocess

import pygtk
pygtk.require('2.0')
import gtk

# Comb modules. First parses the Descript.ion,
# second one writes to file.
import d2utf
import write_utf

# trigger 'info edited' for saving changed info before exit
edited = 0

def info_write():
    """Write info to "desc.utf" file.""" 

    data = []
    def store(model, path, iter, data):
        name = model.get_value(iter, 0)
        comm = model.get_value(iter, 1)
        if comm:
            data.append(str(name) + '||' + str(comm) + '\n')

    temp_data = ['testing',]
    mord.model.foreach(store, data)
    Writer.write_file('desc.utf', data)

def dir_changed(widget):
    """Get text from combo-box, when 'changed' signal is recieved"""

    dir_path = widget.get_active_text() 
    ch_dir(dir_path)
    mord.tree_view.grab_focus()

def ch_dir(dir_path):
    """Change directory, load new list of files to tree-model"""

    print 'it is directory', dir_path
    os.chdir(dir_path)
    desc = read_dir()
    mord.window.set_title(os.getcwd())
    mord.model.clear()
    mord.ins(desc)
    mord.modelfilter.refilter()
    mord.selection.select_path((0,))
    return 0

def col_cb(tv):
    """Callback from column. Set search column"""

    col_num = [0,]
    path, col = tv.get_cursor()
    if col == mord.column2:
        #mord.tree_view.set_search_column(0)
        col_num[0] = 1
    elif col == mord.column:
        #mord.tree_view.set_search_column(1)
        col_num[0] = 0
    return col_num

def entry_cb(ent):
    """Callback for entry widg"""
    
    global search_res
    global res_num
    res_num = 0
#    search_res = []

    col_num = col_cb(mord.tree_view)
    search = ent.get_text()

    data = [search]
    def find_match(model, path, iter, data):

        match_line = model.get_value(iter, col_num[0])
        if match_line.find(data[0]) >= 0:
            data.append(path)
    mord.modelfilter.foreach(find_match, data)
    search_res = data[1:]
    if search_res:
        mord.selection.select_path(search_res[0])
        mord.tree_view.scroll_to_cell(search_res[0])
    else:
        print 'no match found'

def vis(model, iter, small):
    """Callback for the model-filter.

    Show/hide rows that do not contain comments (except for parent dir).

    """

    if small:
        f_name = model.get_value(iter, 0)
        comm = model.get_value(iter, 1)
        if comm or f_name == '/..':
            return True
        else:
            return False 
    else:
        return True 

def on_key_press_event(widget, event):
    """Key-press callback."""
    global res_num
    selection = mord.selection
    model, path = selection.get_selected_rows()
    iter = model.get_iter(path[0])
    f_name = model.get_value(iter, 0) 

    keyname = gtk.gdk.keyval_name(event.keyval)

    # Run file with assotiated programm. 
    if keyname == "Return":
        if f_name[0:1] == '/':
            dir_path = os.path.join(os.getcwd(), f_name[1:])
            ch_dir(dir_path)

        path_ls = []
        f_path = os.path.join(os.getcwd(), f_name)
        mime = mimetypes.guess_type(f_path)[0]
        if debug == 1:
            print 'mime', mime
        if mime:
            res = re.search(r"(.*)/(.*)", mime).groups()
            type = res[0]
            if type == "application":
                type = res[1]
            if debug == 1:
                print 'type', type
        elif f_name[0:1] != '/':
            print 'unknown file type'
            return 1
        else:
            return 1

        print type_dict.items()
        for j in type_dict.items():
            if j[0] == type:
                # prog_ls - programm name and it arguments (list)
                prog_ls = j[1]

        if prog_ls == [] and f_name[0:1] != '/':
            print 'unknown file type'
            return 1

        [path_ls.append(z) for z in prog_ls]
        path_ls.append(f_path)
        print path_ls
        subprocess.Popen(path_ls)
        return 0

    if keyname == "r":
        desc = read_dir()
        mord.model.clear()
        mord.ins(desc)
        mord.modelfilter.refilter()
        mord.selection.select_path((0,))
        return 0

    # show/hide rows without comments
    if keyname == "h":
        if not mord.small:
            mord.small.append(1)
            mord.modelfilter.refilter()
            return 0
        else:
            mord.small.pop()
            mord.modelfilter.refilter()
            return 0

    # save to "desc.utf"
    if keyname == "s" or keyname == "Cyrillic_yeru":
        info_write()
        return 0

    # make a bookmark, save to config file
    if keyname == "b":
        wd_path = os.getcwd() 
        Writer.write_line(conf, [wd_path + '\n',], 'a')
        mord.combo.append_text(wd_path)

    # view next search result
    if keyname == "n":
        res_num += 1
        if len(search_res) > res_num:
            mord.selection.select_path(search_res[res_num])
            mord.tree_view.scroll_to_cell(search_res[res_num])
            mord.tree_view.set_cursor_on_cell(search_res[res_num])

def edited_cb(cell, path, new_text):
    """Save changes in GUI (tree-view) to tree-model."""
    global edited
    edited = 1
    # "Show" mode is on. Save comment to tree-model.
    if not mord.small:
        mord.model[path][1] = new_text
    # "Hide" is on. Take iter from filtered model, convert to basic model,
    # save comment to basic model.
    else:
        filt_iter = mord.modelfilter.get_iter(path)
        iter = mord.modelfilter.convert_iter_to_child_iter(filt_iter)
        mord.model.set_value(iter, 1, new_text)          

def destroy_cb(widget):
    """If window is destroyed and info was changed, save."""

    if edited:
        print 'saving changes'
        info_write()
    gtk.main_quit()
    return False

class Pane:
    """Create GUI and Tree-model behind it"""

    def __init__(self): 
        self.small = [] 
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title(os.getcwd())
        self.window.connect("destroy", destroy_cb) 

        self.window.set_border_width(10)
        self.window.set_size_request(750, 400)

        self.create_list()
        self.window.add(self.vbox)
 
        self.vbox.show()
        self.window.show()


    def create_list(self):
        """Create scrolled window with Tree-view widget"""

        combo_lst = []
        try:
            fp = codecs.open(conf, "rb", "utf8")
            combo_lst = fp.readlines()
            fp.close()
        except IOError:
            print 'no config file'
        
        self.vbox = gtk.VBox()
        self.scrolled_window = gtk.ScrolledWindow()
        self.combo = gtk.combo_box_new_text()
        self.entry = gtk.Entry()
        self.vbox.pack_start(self.combo, False, False, 0)
        self.vbox.pack_start(self.scrolled_window)
        self.vbox.pack_start(self.entry, False, False, 0)
#        self.vbox.pack_start(self.bbox, False)

        for i in combo_lst: 
            self.combo.append_text(i.strip())
        self.combo.connect("changed", dir_changed)       
         
        self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, 
                                        gtk.POLICY_AUTOMATIC)
        
        self.model = gtk.ListStore(str, str, 'gboolean')

        self.tree_view = gtk.TreeView()
        
        self.selection = self.tree_view.get_selection()

        self.modelfilter = self.model.filter_new()
        self.modelfilter.set_visible_func(vis, self.small)
        self.tree_view.set_model(self.modelfilter)

        self.tree_view.set_search_column(1)
#        self.tree_view.set_headers_clickable(True)

        self.tree_view.connect('key_press_event', on_key_press_event)
        self.tree_view.connect('cursor-changed', col_cb)

        self.scrolled_window.add(self.tree_view)
        self.tree_view.show()

        self.cell1 = gtk.CellRendererText()
        self.cell2 = gtk.CellRendererText()

        self.column = gtk.TreeViewColumn("Filenames", self.cell1, text=0)
        self.column2 = gtk.TreeViewColumn("Comments", self.cell2, text=1)

#        self.column.set_clickable(True)
#        self.column2.set_clickable(True)

#        self.column.connect('clicked', col_cb)
#        self.column2.connect('clicked', col_cb)

        # Prevents columns from accumulating width, (usefull, when filenames 
        # are long in previous directory).
        self.column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.column2.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)

        self.tree_view.append_column(self.column)
        self.tree_view.append_column(self.column2)

        self.cell2.set_property('editable', True)       
#        self.cell2.connect('edited', edited_cb, self.model) 
        self.cell2.connect('edited', edited_cb) 
#        self.cell2.connect('editing-started', on_key_press_event) 
#        self.cell2.connect('editing-canceled', edited_cb, 0) 
        
        self.entry.connect('activate', entry_cb)
#        self.entry.set_has_frame(True)
        self.combo.show()
        self.entry.show()
        self.scrolled_window.show_all()

    def ins(self, desc):
        """Insert info into Tree-model"""

        desc_list = desc.items()
        
#        desc_list.sort()
        desc_list.insert(0, ('/..', ''))
        dirs = []
        files = []
        for i in desc_list:
            if '/' in i[0]:
                dirs.append(i)
            else:
                files.append(i)
        dirs.sort()
        files.sort()
        desc_list = dirs + files

        for i in desc_list:
            iter = self.model.append()
            self.model.set(iter, 0, i[0])
            self.model.set(iter, 1, i[1])

    
###################### MAIN ############################

def main():
    gtk.main()
    return 0

if __name__ == '__main__':

    global conf
    global home
    home = os.path.expanduser('~')
    conf = os.path.join(home, '.comb.config')
    try:
        os.path.exists(conf)
    except IOError:
        print 'no config file, creating'
        c_f = open(conf, 'w')
        c_f.close()
    argv = sys.argv
    if len(argv)>1:
        os.chdir(argv[1])

    # debugging option
    debug = 0

    def f_assoc():
        """Get file-associations"""
        fp = open(os.path.join(home, '.comb.bindings'), "r")
        bind_lst = fp.readlines()
        fp.close()

        # dictionary of file-types and associated applications
        global type_dict
        type_dict = {}
        for x in bind_lst:
            if not '#' in x:
                line_lst = x.split()
                arg_ls = []
                for m in range(1, len(line_lst)):
                    arg_ls.append(line_lst[m])
                    type_dict[line_lst[0]] = arg_ls

            else:
                continue
            line_lst = []
        if debug == 1:
            print type_dict
        return type_dict
 
    def get_path():
        """Form a list of all files in directory"""
    
        pth = os.getcwd()
        full_ls = os.listdir(pth)
        for i in range(len(full_ls)):
            if os.path.isdir(full_ls[i]):
                full_ls[i] = '/' + full_ls[i] 
        full_ls.sort()
        return full_ls

    def read_dir():
        """Read filenames and comments.
        
        If there is any text-file of supported type, read. Create a dictionary 
        of filenames and comments. Include names of files, that have no 
        comments to them.
        
        """

        full_ls = get_path()
        desc_tmp = {}
        desc = {}
        if 'desc.utf' in full_ls: 
            print "reading from desc.utf"        
            fp = codecs.open('desc.utf', "rb", "utf8")
            desc_lns = fp.readlines()
            fp.close()
            
            for x in desc_lns:
                # have to encode strings, else unicode filenames will be 
                # duplicated by 'setdefault' dict method (see below).
                lst = x.encode('utf8').strip().split('||')
                try:
                    if lst[0] in full_ls:
                        if lst[1]:
                    
                            # fill in main dictionary
                            desc[lst[0]] = lst[1]
                    else:
                        print 'missing file', lst[0]
                
                except IndexError:
                    print 'desc.utf file is probably corrupted. Wrong separator?' 
            # insert missing file names
            for y in full_ls:
                # if there is no 'y' in desc, it is inserted
                desc.setdefault(y, '')

            return desc
        
        # Parse old file, created by FAR.
        elif 'Descript.ion' in full_ls:
            print "No desc.utf file found, opening Descript.ion."

            # d2utf module function. Parses Descript.ion
            desc = d2utf.desc_func(full_ls)
            for j in desc:
                if not j in full_ls:
                    print 'missing file!', j
 
            for y in full_ls:
                desc.setdefault(y, '')
            return desc            

        else:        
            print "no desc.utf found, creating empty"
            for j in full_ls:
                desc[j] = "" 
            return desc            
    
    desc = read_dir()
    mord = Pane()
    f_assoc()
#    mord.scrolled_window.grab_focus()
    mord.tree_view.grab_focus()
    # Instanse of 'write_get' class from 'write_utf' module
    Writer = write_utf.write_gen()
    mord.ins(desc)
    # Put selection to zero line.
    mord.selection.select_path((0,))
    
    main()
